#include <cnix/errno.h>
#include <cnix/kernel.h>
#include <cnix/string.h>
#include <cnix/module.h>
#include <cnix/elf.h>
#include <cnix/util.h>

module_t *mod_list_head = NULL;
module_t *mod_list_tail = NULL;

static module_t *alloc_module(void)
{
	return elf_malloc(sizeof(module_t));
}

module_t *init_module(const char *name)
{
	int len;
	module_t *m;
	
	if (!(m = alloc_module()))
	{
		return NULL;
	}

	memset(m, 0, sizeof(module_t));
	
	len = strlen(name);

	if (len >= MAX_NAME_LEN)
	{
		len = MAX_NAME_LEN - 1;
	}

	strncpy(m->mod_name, name, len);
	
	if (!mod_list_head)
	{
		mod_list_head = m;
		mod_list_tail = m;
	}
	else
	{
		mod_list_tail->mod_next = m;
		mod_list_tail = m;
	}

	m->mod_refcnt = 1;

	return m;
}


BOOLEAN deinit_module(module_t *m)
{
	module_t *tmp;
	module_t *ptr;
	rel_t *r;
	rel_t *t;

	if (!mod_list_head)
	{
		DIE("impossible, give up...");
		return FALSE;
	}

	for (tmp = mod_list_head, ptr = NULL; tmp; ptr = tmp, tmp = tmp->mod_next)
	{
		if (tmp == m)
		{
			break;
		}
	}

	if (!tmp)
	{
		DIE("Impossible, give up...");
		return FALSE;
	}

	mod_dec_dep_refcn(m);
	m->mod_refcnt--;

	if (m->mod_refcnt > 0)
	{
		return FALSE;
	}

	for (r = m->ext_rel_list; r;)
	{
		t = r->rel_next;
		elf_free(r);
		r = t;
	}

	// remove from the list
	if (tmp == mod_list_head)
	{
		mod_list_head = tmp->mod_next;
	}

	if (tmp == mod_list_tail)
	{
		mod_list_tail = ptr;
	}

	if (ptr)
	{
		ptr->mod_next = tmp->mod_next;
	}

	deinit_all_sym(m);
	deinit_all_segment(m);

	elf_free(tmp);
	
	return TRUE;		
}

static BOOLEAN module_exist(const char *name)
{
        module_t *m;
        
        for (m = mod_list_head; m; m = m->mod_next)
	{
                if (str_eq(m->mod_name, name))
                {
                        return TRUE;
                }
	}

       return FALSE;
}

static BOOLEAN mod_found(module_t *m, module_t *ref)
{
	link_t *node;
	link_t *head;

	if (!(head = m->mod_link))
	{
		return FALSE;
	}

	node = head;
	do 
	{
		if (node->link_mod == ref)
		{
			return TRUE;
		}

		node = node->next;

	} while (node != head);


	return FALSE;
}

BOOLEAN mod_add_dep_list(module_t *m, module_t *ref)
{
	link_t *node;
	link_t *head;

	if (mod_found(m, ref))
	{
		return TRUE;
	}

	if (!(node = (link_t *)elf_malloc(sizeof(link_t))))
	{
		return FALSE;
	}

	memset(node, 0, sizeof(link_t));

	node->link_mod = ref;
	ref->mod_refcnt++;
	
	node->next = node;
	node->prev = node;

	if (!(head = m->mod_link))
	{
		m->mod_link = node;
	}
	else
	{
		node->prev = head->prev;
		node->next = head;

		head->prev->next = node;
		head->prev = node;
	}

	return TRUE;
}

BOOLEAN mod_del_dep_list(module_t *m, module_t *ref)
{
	link_t *node;
	link_t *head;

	if (!(head = m->mod_link))
	{
		return FALSE;
	}

	node = head;
	do
	{
		if (node->link_mod == ref)
		{
			break;
		}

		node = node->next;

	} while (node != head);

	if (node->link_mod != ref)
	{
		return FALSE;
	}

	ref->mod_refcnt--;
	if (ref->mod_refcnt == 0)
	{
		
	}

	if (node != head)
	{
		node->prev->next = node->next;
		node->next->prev = node->prev;
	}
	else
	{
		m->mod_link = NULL;
	}

	elf_free(node);

	return TRUE;
}


void mod_dec_dep_refcn(module_t *m)
{
	link_t *head;
	link_t *node;
	link_t *tmp;

	if (!(head = m->mod_link))
	{
		return;
	}

	node = head;
	while (1)
	{
		tmp = node->next;
		
		node->prev->next = node->next;
		node->next->prev = node->prev;

		node->link_mod->mod_refcnt--;
		elf_free(node);

		if (tmp == node)
		{
			break;
		}

		node = tmp;
	}

	m->mod_link = NULL;
}

int unregister_module(const char *file)
{
	module_t *m;
	int ret;

	for (m = mod_list_head; m; m = m->mod_next)
	{
		if (str_eq(m->mod_name, file))
		{
			break;
		}
	}

	if (m)
	{
		ret = seg_run_exit(m);	
		deinit_module(m);
		return ret;
	}

	return -1;
}

int register_module(const char *file)
{
	Elf32_Ehdr *pHeader;
	module_t *m;
	char *ptr;
	int ret = 0;

        if (!fill_sym_info())
        {
                return -1;
        }

        if (module_exist(file))
        {
                return -EAGAIN;
        }

	if (!(ptr = get_file_buffer(file)))
	{
		return -1;
	}
	
	if (!(m = init_module(file)))
	{
		elf_free(ptr);
		return -1;
	}

	init_sym_hash_table(m);
	
	pHeader = get_elf_header(ptr);
	if (!enum_all_sections(pHeader))
	{
		return -1;
	}
	if (!enum_all_symtab(m, pHeader))
	{
		return -1;
	}
	
        if (!init_all_segment(m))
	{
		return -1;
	}
		
	enum_all_rel(m);

#if 0
	{
	sect_t *sect;
	sym_t *s;
	rel_t *r;

	printf("print section:\n");
	for (sect = sect_begin_iterator(); sect != sect_end_iterator(); sect = sect_iterator())
	{
		print_section(sect);
	}

	printf("print symbol:\n");
	for (s = sym_begin_iterator(m); s != sym_end_iterator(m); s = sym_iterator(m))
	{
		print_symbol(s);
	}

	printf("print reallocate:\n");
	for (r = rel_begin_iterator(); r != rel_end_iterator(); r = rel_iterator())
	{
		print_rel(r);
	}
	}
#endif	

	add_all_sym_to_segment(m);
	if (!rel_all_gobal_vars(m, FALSE))
	{
		ret = -1;
	}

	deinit_section();
	deinit_all_rel();

	elf_free(ptr);

#ifndef TEST_MODULE
        if (ret < 0)
        {
		deinit_module(m);
		return ret;
        }
        
	return seg_run_enter(m);
#else
	return 0;
#endif
}

int sys_init_module(const char *pathname)
{
        int ret;
	char *tmp;

	if (cklimit(pathname))
	{
		return -EFAULT;
	}

	sys_checkname(pathname);

	if(!(tmp = getname(pathname)))
	{
		return -EAGAIN;
	}

        ret = register_module(tmp);
        
        freename(tmp);
        
        return ret;
}

int sys_delete_module(const char *pathname)
{
        int ret;
	char *tmp;

	if (cklimit(pathname))
	{
		return -EFAULT;
	}

	sys_checkname(pathname);

	if(!(tmp = getname(pathname)))
	{
		return -EAGAIN;
	}
        
        ret = unregister_module(tmp);

        freename(tmp);
        
        return ret;
}
